#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <assert.h>
#include "MeshModel.h"
#include "BoundingBox.h"
#include "Vec3.h"
#include "MatUtil.h"

using std::string;
using std::ifstream;
using std::istringstream;

struct FaceIdcs
{
	int v[4];  // vertex data
	int vn[4]; // normal data
	int vt[4]; // texture data

	FaceIdcs()
	{
		for (int i=0; i<4; i++) {
			v[i] = vn[i] = vt[i] = 0;
		}
	}

	FaceIdcs(std::istream & aStream)
	{
		for (int i=0; i<4; i++) {
			v[i] = vn[i] = vt[i] = 0;
		}

		char c;
		for(int i = 0; i < 3; i++) {
			aStream >> std::ws >> v[i] >> std::ws;
			if (aStream.peek() != '/') {
				continue;
			}

			aStream >> c >> std::ws;
			if (aStream.peek() == '/') {
				aStream >> c >> std::ws >> vn[i];
				continue;
			}else {
				aStream >> vt[i];
			}

			if (aStream.peek() != '/') {
				continue;
			}

			aStream >> c >> vn[i];
		}
	}
};

Vec3 vec3fFromStream(std::istream& s)
{
	float x, y, z;
	s >> x >> std::ws >> y >> std::ws >> z;
	return Vec3(x, y, z);
}

Vec2 vec2fFromStream(std::istream& s)
{
	float x, y;
	s >> x >> std::ws >> y;
	return Vec2(x, y);
}

MeshModel::MeshModel(const string& file)
{
	loadFromFile(file);

	CvarSystem& cvars = CvarSystem::getInstance();	

	_modelAxis = cvars.addCvar("m_axis", true);

	_faceNormalDraw = cvars.addCvar("m_face_normals", false);
	_faceNormalColor = cvars.addCvar("m_face_normal_color", CYAN);
	_faceNormalSize = cvars.addCvar("m_face_normal_size", 0.3f);

	_vertexNormalDraw = cvars.addCvar("m_vertex_normals", false);
	_vertexNormalColor = cvars.addCvar("m_vertex_normal_color", YELLOW);
	_vertexNormalSize = cvars.addCvar("m_vertex_normal_size", 0.3f);
}

void MeshModel::loadFromFile(const string& file)
{
	ifstream ifile(file.c_str());

	vector<FaceIdcs> faces;
	vector<Vec3> vertices;
	vector<Vec3> normals;
	
	while (!ifile.eof()) {
		// get line
		string curLine;
		getline(ifile, curLine);

		// read type of the line
		istringstream issLine(curLine);
		string lineType;

		issLine >> std::ws >> lineType;

		// based on the type parse data
		if (lineType == "v") {
			vertices.push_back(vec3fFromStream(issLine));
		}
		else if (lineType == "vn") {
			normals.push_back(vec3fFromStream(issLine));
		}
		else if (lineType == "f") {
			faces.push_back(issLine);
		}
		else if (lineType == "#" || lineType == "") {
			// comment / empty line
		}
		else {
			// unknown line Type
		}
	}

	_vertices = vector<Vec3>(faces.size() * 3);
	
	if(normals.size() != 0) {
		_normals = vector<Vec3>(faces.size() * 3);
	}

	int k=0;
	float xmin = FLT_MAX, ymin = FLT_MAX, zmin = FLT_MAX, xmax = FLT_MIN, ymax = FLT_MIN, zmax = FLT_MIN;
	
	for (vector<FaceIdcs>::iterator it = faces.begin(); it != faces.end(); ++it) {
		for (int i = 0; i < 3; i++) {
			_vertices[k] = vertices[it->v[i]-1];
			
			if(normals.size() != 0){
				_normals[k] = normals[it->vn[i]-1];
			}

			if(_vertices[k].x < xmin) xmin = _vertices[k].x;
			if(_vertices[k].y < ymin) ymin = _vertices[k].y;
			if(_vertices[k].z < zmin) zmin = _vertices[k].z;
			if(_vertices[k].x > xmax) xmax = _vertices[k].x;
			if(_vertices[k].y > ymax) ymax = _vertices[k].y;
			if(_vertices[k].z > zmax) zmax = _vertices[k].z;

			k++;
		}
	}

	_boundingBox = ModelPtr(new BoundingBox(xmin, ymin, zmin, xmax, ymax, zmax));

	_translations = MatUtil::translate((xmin + xmax) / 2.0f, (ymin + ymax) / 2.0f, (zmin + zmax) / 2.0f);
}

void MeshModel::render(RendererPtr renderer)
{
	assert(_vertices.size() % 3 == 0);
	
	for (int i = 0; i < (int) (_vertices.size() - 2); i += 3) {
		Vec3 v1(_vertices[i + 0]);
		Vec3 v2(_vertices[i + 1]);
		Vec3 v3(_vertices[i + 2]);

		renderer->drawTriangle(v1, v2, v3);

		if(_faceNormalDraw->getValueB()) {
			int color = _faceNormalColor->getValueI();
			float size = _faceNormalSize->getValueF();
			
			Vec3 center = (v1 + v2 + v3)/3;
			Vec3 normal = (v1-v2).cross(v2-v3).normalize();
			renderer->drawLine(center, center+normal*size, color);
		}

		if(_vertexNormalDraw->getValueB() && i < (int) (_normals.size() - 2)) {
			Vec3 n1 = _normals[i + 0];
			Vec3 n2 = _normals[i + 1];
			Vec3 n3 = _normals[i + 2];

			int color = _vertexNormalColor->getValueI();
			float size = _vertexNormalSize->getValueF();			

			renderer->drawLine(v1, v1+n1.normalize()*size, color);
			renderer->drawLine(v2, v2+n2.normalize()*size, color);
			renderer->drawLine(v3, v3+n3.normalize()*size, color);
		}
	}
	
	_boundingBox->render(renderer);

	if (_modelAxis->getValueB()) {
		renderer->setModelTransform(_translations * _rotations);
		renderer->drawLine(Vec3(), Vec3(1.0f, 0.0f, 0.0f), RED);
		renderer->drawLine(Vec3(), Vec3(0.0f, 1.0f, 0.0f), GREEN);
		renderer->drawLine(Vec3(), Vec3(0.0f, 0.0f, 1.0f), BLUE);
	}
}

void MeshModel::translate(float x, float y, float z)
{
	Model::translate(x, y, z);

	_boundingBox->translate(x, y, z);	
}

void MeshModel::scale(float x, float y, float z)
{
	Model::scale(x, y, z);

	_boundingBox->scale(x, y, z);
}

void MeshModel::rotate(float x, float y, float z)
{
	Model::rotate(x, y, z);

	_boundingBox->rotate(x, y, z);
}